using System;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using AUSRIS.Reports.Utilities.Deployment.Entities;

namespace AUSRIS.Reports.Utilities.Deployment.Configuration.Xml
{
    public class XmlConfigurationDatabase : IConfigurationDatabase
    {
        #region Private Fields

        private StringDictionary globalVariables;
        private static readonly Regex GlobalsRegex = new Regex(@"(?<g>\${(?<k>[^}]+)})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private string configFileDirectory;
        private Dictionary<string, DataSource> datasources;

        #endregion

        #region Public Properties

        /// <summary>
        /// Folder that the Configuration file lives in
        /// </summary>
        public string ConfigFileDirectory
        {
            get
            {
                return configFileDirectory;
            }
            set
            {
                configFileDirectory = value;
            }
        }

        #endregion

        #region IConfigurationDatabase Members

        /// <summary>
        /// Creates <see cref="DeploymentSet">DeploymentSet</see> from the Deployment Set's Configuration xml file
        /// </summary>
        /// <param name="name">Name of the Deployment Set configuration to create</param>
        /// <returns><see cref="DeploymentSet"/></returns>
        public DeploymentSet GetDeploymentSet(string name)
        {
            // Create a dictionary collection of datasources
            this.datasources = new Dictionary<string, DataSource>();
            // Create the full path with file name and .xml extension from the
            // configFileDirectory member value and the local name variable value
            string filePath = Path.Combine(this.configFileDirectory, name + ".xml");
            XmlDocument document = null;
            FileInfo f = new FileInfo(filePath);
            // Read the configuration file into a FileStream
            using(FileStream reader = f.OpenRead())
            {
                // Instantiate the XmlDocument and load it with contents from the FileStream
                document = new XmlDocument();
                document.Load(reader);
            }
            // Reads all Global Variables from the XmlDocument 'document' and
            // populates the private StringDictionary globalVariables member with them 
            this.ReadGlobals(document);
            DeploymentSet deploymentSet = this.LoadDeploymentSet(document);
            return deploymentSet;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Loads the Deployment Set with the correct property values
        /// </summary>
        /// <param name="document"></param>
        /// <returns>Instance of a <see cref="DeploymentSet"/> with appropriate property values set</returns>
        private DeploymentSet LoadDeploymentSet(XmlDocument document)
        {
            XmlNodeList deploymentSetNodes = document.SelectNodes("//deployment");
            if(deploymentSetNodes == null || deploymentSetNodes.Count == 0)
            {
                throw new Exception("Deployment Not Found");
            }
            XmlNode deploymentNode = deploymentSetNodes[0];
            DeploymentSet deploymentSet = new DeploymentSet();
            deploymentSet.Name = this.NodeAttribute(deploymentNode, "name");
            deploymentSet.VersionTag = this.NodeAttribute(deploymentNode, "version-tag");
            deploymentSet.ModuleName = this.NodeAttribute(deploymentNode, "module-name");
            deploymentSet.TargetServiceUrl = this.NodeAttribute(deploymentNode, "target-service-url");
            XmlNodeList folderNodes = deploymentNode.SelectNodes("folders/folder");
            if(folderNodes == null)
            {
                throw new Exception("Folders Node Not Found");
            }
            // Creates the folder structure on the target reporting server
            foreach(XmlNode folderNode in folderNodes)
            {
                this.CreateFolder(folderNode, deploymentSet);
            }
            // Adds/Copies Data Sources to the appropriate folders
            foreach(XmlNode folderNode in folderNodes)
            {
                this.AddDataSources(folderNode, deploymentSet);
            }
            // 
            foreach(XmlNode folderNode in folderNodes)
            {
                this.AddFolderContents(folderNode, deploymentSet);
            }
            // 
            foreach(XmlNode folderNode in folderNodes)
            {
                this.AddFolderProperties(folderNode, deploymentSet);
            }

            return deploymentSet;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="folderNode"></param>
        /// <param name="deploymentSet"></param>
        private void CreateFolder(XmlNode folderNode, DeploymentSet deploymentSet)
        {
            string path = this.NodeAttribute(folderNode, "path");
            deploymentSet.Folders.AddFoldersInPath(path);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="folderNode"></param>
        /// <param name="deploymentSet"></param>
        private void AddDataSources(XmlNode folderNode, DeploymentSet deploymentSet)
        {
            string path = this.NodeAttribute(folderNode, "path");
            Folder folder = deploymentSet.Folders[path];
            XmlNodeList dataSourceNodes = folderNode.SelectNodes("data-sources/data-source");
            if(dataSourceNodes != null)
            {
                foreach(XmlNode dataSourceNode in dataSourceNodes)
                {
                    this.AddDataSource(dataSourceNode, folder);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="folderNode"></param>
        /// <param name="deploymentSet"></param>
        private void AddFolderProperties(XmlNode folderNode, DeploymentSet deploymentSet)
        {
            string path = this.NodeAttribute(folderNode, "path");
            Folder folder = deploymentSet.Folders[path];
            this.SetOptionalItemProperties(folder, folderNode);
        }

        /// <summary>
        /// Adds report files, resources and/or report models to appropriate folders
        /// </summary>
        /// <param name="folderNode">XmlNode that represents the folder to add contents to</param>
        /// <param name="deploymentSet"><see cref="DeploymentSet">DeploymentSet</see> that the deployment is processing</param>
        private void AddFolderContents(XmlNode folderNode, DeploymentSet deploymentSet)
        {
            string path = this.NodeAttribute(folderNode, "path");
            Folder folder = deploymentSet.Folders[path];
            XmlNodeList reportNodes = folderNode.SelectNodes("reports/report");
            // Add report file(s) to folders
            if(reportNodes != null)
            {
                foreach(XmlNode reportNode in reportNodes)
                {
                    this.AddReport(reportNode, folder);
                }
            }
            // Add resource file(s) to folders
            XmlNodeList resourceNodes = folderNode.SelectNodes("resources/resource");
            if(resourceNodes != null)
            {
                foreach(XmlNode resourceNode in resourceNodes)
                {
                    this.AddResource(resourceNode, folder);
                }
            }
            // Add report model(s) to folder
            XmlNodeList modelNodes = folderNode.SelectNodes("models/model");
            if(modelNodes != null)
            {
                foreach(XmlNode modelNode in modelNodes)
                {
                    this.AddModel(modelNode, folder);
                }
            }
        }

        /// <summary>
        /// Add resource file(s) to folder
        /// </summary>
        /// <param name="resourceNode">XmlNode that represents the resource to place in folder</param>
        /// <param name="folder"><see cref="Folder">Folder</see> to add files to</param>
        private void AddResource(XmlNode resourceNode, Folder folder)
        {
            string name = this.NodeAttribute(resourceNode, "name");
            string mimeType = this.NodeAttribute(resourceNode, "mime-type");
            XmlNode sourceNode = resourceNode.SelectSingleNode("source-path");
            string sourcePath = this.NodeValue(sourceNode);
            Resource resource = new Resource(name, folder, sourcePath, mimeType);
            this.SetOptionalItemProperties(resource, resourceNode);
            folder.Files.Add(resource);
        }

        /// <summary>
        /// Add report model(s) to folder
        /// </summary>
        /// <param name="modelNode">XmlNode that represents the report model to place in folder</param>
        /// <param name="folder"><see cref="Folder">Folder</see> to add files to</param>
        private void AddModel(XmlNode modelNode, Folder folder)
        {
            string name = this.NodeAttribute(modelNode, "name");
            XmlNode sourceNode = modelNode.SelectSingleNode("source-path");
            string sourcePath = this.NodeValue(sourceNode);
            Model model = new Model(name, folder, sourcePath);

            string dataSourceName = this.NodeAttribute(modelNode, "data-source");
            if(!this.datasources.ContainsKey(dataSourceName))
            {
                throw new Exception(string.Format("unknown data-source '{0}' specified in {1}", dataSourceName, model.Name));
            }
            model.DataSource = this.datasources[dataSourceName];

            XmlNode dsvNode = modelNode.SelectSingleNode("datasourceview-path");
            model.DataSourceViewPath = this.NodeValue(dsvNode);

            this.SetOptionalItemProperties(model, modelNode);

            folder.Files.Add(model);
        }

        /// <summary>
        /// Add report file(s) to folder, sets its datasource and other report properties
        /// </summary>
        /// <param name="reportNode">XmlNode that represents the report model to place in folder</param>
        /// <param name="folder"><see cref="Folder">Folder</see> to add report files to</param>
        private void AddReport(XmlNode reportNode, Folder folder)
        {
            if(reportNode.Attributes["name"] == null)
            {
                throw new Exception(string.Format("Report with no name specified : {0}", reportNode.InnerText));
            }
            string name = this.NodeAttribute(reportNode, "name");

            string collapsedHeight = "";
            if(reportNode["collapsed-height"] != null)
            {
                collapsedHeight = this.NodeAttribute(reportNode, "collapsed-height");
            }

            int cacheTime = 0;
            if(reportNode["cache-time"] != null)
            {
                cacheTime = int.Parse(this.NodeAttribute(reportNode, "cache-time"));
            }

            XmlNode sourceNode = reportNode.SelectSingleNode("source-path");
            string sourcePath = this.NodeValue(sourceNode);

            Report report = new Report(name, folder, sourcePath, collapsedHeight, cacheTime);
            this.SetOptionalItemProperties(report, reportNode);

            if(reportNode.Attributes["data-source"] == null)
            {
                throw new Exception(string.Format("data-source not specified for report {0}", name));
            }
            string dataSourceName = this.NodeAttribute(reportNode, "data-source");
            if(!this.datasources.ContainsKey(dataSourceName))
            {
                throw new Exception(string.Format("unknown data-source '{0}' specified in {1}", dataSourceName, report.Name));
            }
            report.DataSource = this.datasources[dataSourceName];

            XmlNodeList parameterNodes = reportNode.SelectNodes("parameter");
            if(parameterNodes != null)
            {
                foreach(XmlNode node in parameterNodes)
                {
                    this.AddParameter(node, report);
                }
            }

            folder.Files.Add(report);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fromNode"></param>
        /// <param name="item"></param>
        private void AddPolicy(XmlNode fromNode, DeploymentItem item)
        {
            ItemPolicy policy = new ItemPolicy(this.NodeAttribute(fromNode, "group-user-name"));
            string[] roleNames = fromNode.Attributes["roles"].Value.Split(',');
            foreach(string roleName in roleNames)
            {
                policy.Roles.Add(new ItemPolicyRole(roleName.Trim()));
            }
            item.Policies.Add(policy);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="report"></param>
        private void AddParameter(XmlNode node, Report report)
        {
            string name = this.NodeAttribute(node, "name");
            XmlNode promptNode = node.Attributes["prompt-user"];
            bool promptUser = false;
            if(promptNode != null && promptNode.Value.Length > 0)
            {
                promptUser = bool.Parse(this.ProcessGlobals(promptNode.Value));
            }
            XmlNode defaultNode = node.Attributes["default-values"];
            string[] defaults = null;
            if(defaultNode != null)
            {
                defaults = node.Attributes["default-values"].Value.Split(',');
            }
            Parameter parameter = new Parameter(name, promptUser, defaults);
            report.Parameters.Add(parameter);
        }

        /// <summary>
        /// Add Datasource(s) to <see cref="Folder">Folder</see>
        /// </summary>
        /// <param name="node"><see cref="XmlNode"/> with attributes that hold the values for the DataSource properties</param>
        /// <param name="folder">Instance of a <see cref="Folder">Folder</see> to add DataSource to</param>
        private void AddDataSource(XmlNode node, Folder folder)
        {
            string name = null;
            string userName = null;
            string password = null;
            string credentialRetrieval = null;
            string connectionString = null;
            bool publish = false;
            bool overwrite = false;
            bool windowsCredentials = false;

            XmlNode n1 = node.Attributes["name"];

            if(n1 != null)
            {
                name = ProcessGlobals(n1.Value);
                XmlNode n2 = node.Attributes["publish"];
                XmlNode n3 = node.SelectSingleNode("connection-string");
                XmlNode n4 = node.Attributes["overwrite"];
                XmlNode n5 = node.SelectSingleNode("username");
                XmlNode n6 = node.SelectSingleNode("password");
                XmlNode n7 = node.SelectSingleNode("credential-retrieval");
                XmlNode n8 = node.SelectSingleNode("windows-credentials");

                if(n2 != null)
                {
                    publish = (n2.Value.ToLower() == "true");
                }
                if(n3 != null)
                {
                    connectionString = ProcessGlobals(n3.InnerText);
                }
                if(n4 != null)
                {
                    overwrite = (n4.Value.ToLower() == "true");
                }
                if(n5 != null)
                {
                    if(n5.InnerText.Trim().Length > 0)
                    {
                        userName = ProcessGlobals(n5.InnerText);
                    }
                }
                if(n6 != null)
                {
                    password = ProcessGlobals(n6.InnerText);
                }
                if(n7 != null)
                {
                    credentialRetrieval = ProcessGlobals(n7.InnerText);
                }
                if(n8 != null)
                {
                    windowsCredentials = (n8.InnerText.ToLower() == "true");
                }

                // Create DataSource with given paramater values
                DataSource datasource = new DataSource(name, folder, userName, password, credentialRetrieval, windowsCredentials, connectionString, publish, overwrite);
                // Place 'datasource' in the 'folder'
                folder.Datasources.Add(datasource);
                // Add 'datasource' to the 'datasources' Dictionary
                this.datasources.Add(name, datasource);
            }
        }

        /// <summary>
        /// Sets other optional properties on the DeploymentItem
        /// </summary>
        /// <param name="item"><see cref="DeployItem"/> to have it's properties set</param>
        /// <param name="sourceNode"><see cref="XmlNode"/> whose attribute values are used to set the <see cref="DeployItem"/>'s properties</param>
        private void SetOptionalItemProperties(DeploymentItem item, XmlNode sourceNode)
        {
            if(sourceNode.Attributes["hidden"] != null && sourceNode.Attributes["hidden"].Value.Length > 0)
            {
                item.Hidden = bool.Parse(this.NodeAttribute(sourceNode, "hidden"));
            }
            if(sourceNode["description"] != null)
            {
                item.Description = this.NodeValue(sourceNode["description"]);
            }
            XmlNodeList policyList = sourceNode.SelectNodes("policies/policy");
            if(policyList != null)
            {
                foreach(XmlNode policyNode in policyList)
                {
                    this.AddPolicy(policyNode, item);
                }
            }
        }

        /// <summary>
        /// Pulls the given <see cref="XmlNode"/> attributes value's and returns as a string
        /// </summary>
        /// <param name="node"></param>
        /// <param name="name"></param>
        /// <returns><see cref="string"/></returns>
        private string NodeAttribute(XmlNode node, string name)
        {
            return this.ProcessGlobals(node.Attributes[name].Value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private string NodeValue(XmlNode node)
        {
            return this.ProcessGlobals(node.InnerText);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string ProcessGlobals(string input)
        {
            if(string.IsNullOrEmpty(input))
            {
                return null;
            }
            string output = input;
            MatchCollection matches = GlobalsRegex.Matches(input);
            output = GlobalsRegex.Replace(output, new MatchEvaluator(this.ReplaceMatches));
            return output;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        private string ReplaceMatches(Match match)
        {
            string key = match.Groups["k"].ToString();
            string toReplace = match.Groups["g"].ToString();
            string output = toReplace;
            if(globalVariables.ContainsKey(key))
            {
                output = globalVariables[key];
            }
            return output;
        }

        /// <summary>
        /// Reads all the global property values from the <see cref="XmlDocument"/> and adds them to the globalVariables member
        /// </summary>
        /// <param name="fromDoc"><see cref="XmlDocument"/> to read global variable values from</param>
        private void ReadGlobals(XmlDocument fromDoc)
        {
            XmlNodeList globalNodes = fromDoc.SelectNodes("//deployment/globals/global");
            globalVariables = new StringDictionary();
            
            if(globalNodes != null)
            {
                foreach(XmlNode node in globalNodes)
                {
                    XmlNode key = node.Attributes["name"];
                    if(key != null)
                    {
                        if(globalVariables.ContainsKey(key.Value))
                        {
                            globalVariables[key.Value] = node.InnerText;
                        }
                        else
                        {
                            globalVariables.Add(key.Value, node.InnerText);
                        }
                    }
                }
            }
        }

        #endregion
    }
}